/*
 * Copyright (C) 2008-2012 TrinityCore <http://www.trinitycore.org/>
 * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef TRINITY_UNITAI_H
#define TRINITY_UNITAI_H

//#include "Containers.h"
#include "Unit.h"

class Unit;
class Player;
class Spell;
struct AISpellInfoType;

// Default script texts
enum GeneralScriptTexts
{
    DEFAULT_TEXT                = -1000000,
    EMOTE_GENERIC_FRENZY_KILL   = -1000001,
    EMOTE_GENERIC_FRENZY        = -1000002,
    EMOTE_GENERIC_ENRAGED       = -1000003,
    EMOTE_GENERIC_BERSERK       = -1000004,
    EMOTE_GENERIC_BERSERK_RAID  = -1000005 // RaidBossEmote version of the previous one
};

//Selection method used by SelectTarget
enum SelectAggroTarget
{
    SELECT_TARGET_RANDOM = 0,                               //Just selects a random target
    SELECT_TARGET_TOPAGGRO,                                 //Selects targes from top aggro to bottom
    SELECT_TARGET_BOTTOMAGGRO,                              //Selects targets from bottom aggro to top
    SELECT_TARGET_NEAREST,
    SELECT_TARGET_FARTHEST
};

// default predicate function to select target based on distance, player and/or aura criteria
struct DefaultTargetSelector : public std::unary_function<Unit*, bool>
{
    const Unit* me;
    float m_dist;
    bool m_playerOnly;
    int32 m_aura;

    // unit: the reference unit
    // dist: if 0: ignored, if > 0: maximum distance to the reference unit, if < 0: minimum distance to the reference unit
    // playerOnly: self explaining
    // aura: if 0: ignored, if > 0: the target shall have the aura, if < 0, the target shall NOT have the aura
    DefaultTargetSelector(Unit const* unit, float dist, bool playerOnly, int32 aura);

    bool operator()(Unit const* target) const;
};

// Target selector for spell casts checking range, auras and attributes
// TODO: Add more checks from Spell::CheckCast
struct SpellTargetSelector : public std::unary_function<Unit*, bool>
{
    public:
        SpellTargetSelector(Unit* caster, uint32 spellId);
        bool operator()(Unit const* target) const;

    private:
        Unit const* _caster;
        SpellInfo const* _spellInfo;
};

// Very simple target selector, will just skip main target
// NOTE: When passing to UnitAI::SelectTarget remember to use 0 as position for random selection
//       because tank will not be in the temporary list
struct NonTankTargetSelector : public std::unary_function<Unit*, bool>
{
    public:
        NonTankTargetSelector(Creature* source, bool playerOnly = true) : _source(source), _playerOnly(playerOnly) { }
        bool operator()(Unit const* target) const;

    private:
        Creature const* _source;
        bool _playerOnly;
};

struct TankTargetSelector : public std::unary_function<Unit*, bool>
{
    public:
        TankTargetSelector(Creature* source, bool playerOnly = true) : _source(source), _playerOnly(playerOnly) { }
        bool operator()(Unit const* target) const;

    private:
        Creature const* _source;
        bool _playerOnly;
};

class UnitAI
{
    protected:
        Unit* const me;
    public:
        explicit UnitAI(Unit* unit) : me(unit) {}
        virtual ~UnitAI() {}

        virtual bool CanAIAttack(Unit const* /*target*/) const { return true; }
        virtual void AttackStart(Unit* /*target*/);
        virtual void UpdateAI(uint32 diff) = 0;

        virtual void InitializeAI() { if (!me->isDead()) Reset(); }

        virtual void Reset() {};

        // Called when unit is charmed
        virtual void OnCharmed(bool apply) = 0;

        // Called at Unit::SelectVictim at ThreatContainer::selectNextVictim
        virtual bool AllowSelectNextVictim(Unit* target) { return true; }

        // Pass parameters between AI
        virtual void DoAction(int32 const /*param*/) {}
        virtual void DoAction(int32 const /*param*/, Creature* /*creature*/) {}
        virtual uint32 GetData(uint32 /*id = 0*/) const{ return 0; }
        virtual bool GetObjectData(ObjectGuid const& /*guid*/) { return false; }
        virtual bool GetObjectData(ObjectGuid const& /*guid*/, uint32 type) { return false; }
        virtual uint32 GetModifyedData(uint32 /*id = 0*/) { return 0; } //non const for some modifications and getting data. Have 1 script with it.
        virtual uint64 GetData64(uint64 /*id = 0*/) const{ return 0; }
        virtual void SetData(uint32 /*id*/, uint32 /*value*/) {}
        virtual void SetGUID(ObjectGuid const& /*guid*/, int32 /*id*/ = 0) {}
        virtual ObjectGuid GetGUID(int32 /*id*/ = 0) { return ObjectGuid::Empty; }

        virtual bool IsDisableGenerateLoot() { return false; }

        Unit* SelectTarget(SelectAggroTarget targetType, uint32 position = 0, float dist = 0.0f, bool playerOnly = false, int32 aura = 0);
        // Select the targets satisfying the predicate.
        // predicate shall extend std::unary_function<Unit*, bool>
        template <class PREDICATE> Unit* SelectTarget(SelectAggroTarget targetType, uint32 position, PREDICATE const& predicate)
        {
            const std::list<HostileReference*>& threatlist = me->getThreatManager().getThreatList();
            if (position >= threatlist.size())
                return nullptr;

            std::list<Unit*> targetList;
            for (std::list<HostileReference*>::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
                if (predicate((*itr)->getTarget()))
                    targetList.push_back((*itr)->getTarget());

            if (position >= targetList.size())
                return nullptr;

            if (targetType == SELECT_TARGET_NEAREST || targetType == SELECT_TARGET_FARTHEST)
                targetList.sort(Trinity::ObjectDistanceOrderPred(me));

            switch (targetType)
            {
                case SELECT_TARGET_NEAREST:
                case SELECT_TARGET_TOPAGGRO:
                {
                    std::list<Unit*>::iterator itr = targetList.begin();
                    std::advance(itr, position);
                    return *itr;
                }
                case SELECT_TARGET_FARTHEST:
                case SELECT_TARGET_BOTTOMAGGRO:
                {
                    std::list<Unit*>::reverse_iterator ritr = targetList.rbegin();
                    std::advance(ritr, position);
                    return *ritr;
                }
                case SELECT_TARGET_RANDOM:
                {
                    std::list<Unit*>::iterator itr = targetList.begin();
                    std::advance(itr, urand(position, targetList.size() - 1));
                    return *itr;
                }
                default:
                    break;
            }

            return nullptr;
        }

        void SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectAggroTarget targetType, float dist = 0.0f, bool playerOnly = false, int32 aura = 0);

        // Select the targets satifying the predicate.
        // predicate shall extend std::unary_function<Unit*, bool>
        template <class PREDICATE> void SelectTargetList(std::list<Unit*>& targetList, PREDICATE const& predicate, uint32 maxTargets, SelectAggroTarget targetType)
        {
            std::list<HostileReference*> const& threatlist = me->getThreatManager().getThreatList();
            if (threatlist.empty())
                return;

            for (std::list<HostileReference*>::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
                if (predicate((*itr)->getTarget()))
                    targetList.push_back((*itr)->getTarget());

            if (targetList.size() < maxTargets)
                return;

            if (targetType == SELECT_TARGET_NEAREST || targetType == SELECT_TARGET_FARTHEST)
                targetList.sort(Trinity::ObjectDistanceOrderPred(me));

            if (targetType == SELECT_TARGET_FARTHEST || targetType == SELECT_TARGET_BOTTOMAGGRO)
                targetList.reverse();

            if (targetType == SELECT_TARGET_RANDOM)
                Trinity::Containers::RandomResizeList(targetList, maxTargets);
            else
                targetList.resize(maxTargets);
        }

        // Called at any Damage to any victim (before damage apply)
        virtual void DamageDealt(Unit* /*victim*/, uint32& /*damage*/, DamageEffectType /*damageType*/) { }

        // Called at any Damage from any attacker (before damage apply)
        // Note: it for recalculation damage or special reaction at damage
        // for attack reaction use AttackedBy called for not DOT damage in Unit::DealDamage also
        virtual void DamageTaken(Unit* /*attacker*/, uint32& /*damage*/, DamageEffectType dmgType) {}

        // Called when the creature receives heal
        virtual void HealReceived(Unit* /*done_by*/, uint32& /*addhealth*/) {}

        // Called when the unit heals
        virtual void HealDone(Unit* /*done_to*/, uint32& /*addhealth*/) {}

        void AttackStartCaster(Unit* victim, float dist);

        void DoAddAuraToAllHostilePlayers(uint32 spellid);
        void DoCast(uint32 spellId);
        void DoCast(Unit* victim, uint32 spellId, bool triggered = false);
        void DoCastToAllHostilePlayers(uint32 spellid, bool triggered = false);
        void DoFunctionToHostilePlayers(uint8 playersCount, std::function<void(Unit*, Player*)> const& function);
        void DoCastVictim(uint32 spellId, bool triggered = false);
        void DoCastTopAggro(uint32 spellId, bool triggered = false, bool onlyPlayer = true);
        void DoCastAOE(uint32 spellId, bool triggered = false);

        float DoGetSpellMaxRange(uint32 spellId, bool positive = false);

        bool DoMeleeAttackIfReady(SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL);
        bool DoSpellAttackIfReady(uint32 spell, TriggerCastFlags triggerFlags = TRIGGERED_NONE, Unit* target = nullptr, bool noDelay = false);

        static AISpellInfoType* AISpellInfo;
        static void FillAISpellInfo();

        virtual void sGossipHello(Player* /*player*/) {}
        virtual void sGossipSelect(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/) {}
        virtual void sGossipSelectCode(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/, char const* /*code*/) {}
        virtual void sQuestAccept(Player* /*player*/, Quest const* /*quest*/) {}
        virtual void sQuestSelect(Player* /*player*/, Quest const* /*quest*/) {}
        virtual void sQuestComplete(Player* /*player*/, Quest const* /*quest*/) {}
        virtual void sQuestReward(Player* /*player*/, Quest const* /*quest*/, uint32 /*opt*/) {}
        virtual bool sOnDummyEffect(Unit* /*caster*/, uint32 /*spellId*/, SpellEffIndex /*effIndex*/) { return false; }

        virtual void sOnActivateTaxiPathTo(Player* /*player*/, uint32 /*patchid*/) { }

        void AddDelayedEvent(Minutes delayTime, std::function<void()> && function);
        void AddDelayedEvent(Seconds delayTime, std::function<void()> && function);
        void KillAllDelayedEvents();
};

class  PlayerAI : public UnitAI
{
public:
    explicit PlayerAI(Player* player, Creature* o = nullptr) : UnitAI(static_cast<Unit*>(player)), me(player), owner(o), _selfSpec(PlayerAI::GetPlayerSpec(player)), _isSelfHealer(PlayerAI::IsPlayerHealer(player)), _isSelfRangedAttacker(PlayerAI::IsPlayerRangedAttacker(player)) { }

    void OnCharmed(bool /*apply*/) override; // charm AI application for players is handled by Unit::SetCharmedBy / Unit::RemoveCharmedBy
    bool UpdateVictim();
    // helper functions to determine player info
    // Return values range from 0 (left-most spec) to 2 (right-most spec). If two specs have the same number of talent points, the left-most of those specs is returned.
    static uint8 GetPlayerSpec(Player* who);
    // Return values range from 0 (left-most spec) to 2 (right-most spec). If two specs have the same number of talent points, the left-most of those specs is returned.
    uint8 GetSpec(Player* who = nullptr) { return (!who || who == me) ? _selfSpec : GetPlayerSpec(who); }
    static bool IsPlayerHealer(Player* who);
    bool IsHealer(Player* who = nullptr) { return (!who || who == me) ? _isSelfHealer : IsPlayerHealer(who); }
    static bool IsPlayerRangedAttacker(Player* who);
    bool IsRangedAttacker(Player* who = nullptr) { return (!who || who == me) ? _isSelfRangedAttacker : IsPlayerRangedAttacker(who); }

protected:
    struct TargetedSpell : public std::pair<Spell*, Unit*>
    {
        TargetedSpell() : pair<Spell*, Unit*>() { }
        TargetedSpell(Spell* first, Unit* second) : pair<Spell*, Unit*>(first, second) { }
        explicit operator bool() { return !!first; }
    };
    typedef std::pair<TargetedSpell, uint32> PossibleSpell;
    typedef std::vector<PossibleSpell> PossibleSpellVector;

    Player* const me;
    Creature* const owner;

    void SetIsRangedAttacker(bool state) { _isSelfRangedAttacker = state; } // this allows overriding of the default ranged attacker detection

    enum SpellTarget
    {
        TARGET_NONE,
        TARGET_VICTIM,
        TARGET_CHARMER,
        TARGET_SELF
    };
    /* Check if the specified spell can be cast on that target.
       Caller is responsible for cleaning up created Spell object from pointer. */
    TargetedSpell VerifySpellCast(uint32 spellId, Unit* target);
    /* Check if the specified spell can be cast on that target.
    Caller is responsible for cleaning up created Spell object from pointer. */
    TargetedSpell VerifySpellCast(uint32 spellId, SpellTarget target);

    /* Helper method - checks spell cast, then pushes it onto provided vector if valid. */
    template<typename T> inline void VerifyAndPushSpellCast(PossibleSpellVector& spells, uint32 spellId, T target, uint32 weight)
    {
        if (TargetedSpell spell = VerifySpellCast(spellId, target))
            spells.push_back({ spell,weight });
    }

    /* Helper method - selects one spell from the vector and returns it, while deleting everything else.
       This invalidates the vector, and empties it to prevent accidental misuse. */
    TargetedSpell SelectSpellCast(PossibleSpellVector& spells);
    /* Helper method - casts the included spell at the included target */
    inline void DoCastAtTarget(TargetedSpell spell);

    virtual Unit* SelectAttackTarget() const { return me->GetCharmer() ? me->GetCharmer()->getVictim() : nullptr; }
    void DoRangedAttackIfReady();
    void DoAutoAttackIfReady();

    // Cancels all shapeshifts that the player could voluntarily cancel
    void CancelAllShapeshifts();

private:
    uint8 const _selfSpec;
    bool const _isSelfHealer;
    bool _isSelfRangedAttacker;
};

class SimpleCharmedAI : public PlayerAI
{
    public:
        void UpdateAI(uint32 diff);
        SimpleCharmedAI(Player* player): PlayerAI(player) {}
};

#endif
